理解 ==、equals 和 hashcode 的区别

==

java 中的数据类型,可分为两类:

1、基本数据类型
byte,short,char,int,long,float,double,boolean 他们之间的比较,应用双等号(==),比较的是他们的值。

2、引用类型(类、接口、数组)
当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个 new 出来的对象,他们的比较后的结果为 true,否则比较后结果为 false。

对象是放在堆中的,栈中存放的是对象的引用(地址)。由此可见 ‘==’ 是对栈中的值进行比较的。如果要比较堆中对象的内容是否相同,那么就要重写 equals 方法了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void main(String[] args) {
int int1 = 12;
int int2 = 12;
Integer Integer1 = new Integer(12);
Integer Integer2 = new Integer(12);
Integer Integer3 = new Integer(127);
Integer a1 = 127;
Integer b1 = 127;
Integer a = 128;
Integer b = 128;
String s1 = "str";
String s2 = "str";
String str1 = new String("str");
String str2 = new String("str");
System.out.println("int1 == int2: " + (int1 == int2));
System.out.println("int1 == Integer1: " + (int1 == Integer1));
System.out.println("Integer1 == Integer2: " + (Integer1 == Integer2));
System.out.println("Integer3 == b1: " + (Integer3 == b1));
System.out.println("a1 == b1: " + (a1 == b1));
System.out.println("a == b: " + (a == b));
System.out.println("s1 == s2: " + (s1 == s2));
System.out.println("s1 == str1: " + (s1 == str1));
System.out.println("str1 == str2: " + (str1 == str2));
}

输出结果为:
int1 == int2: true
int1 == Integer1: true
Integer1 == Integer2: false
Integer3 == b1: false
a1 == b1: true
a == b: false
s1 == s2: true
s1 == str1: false
str1 == str2: false

equals

1、默认情况(没有覆盖 equals 方法)
equals 方法都是调用 Object 类的 equals 方法,而 Object 的 equals 方法主要用于判断对象的内存地址引用是不是同一个地址(是不是同一个对象)。下面是 Object 类中 equals 方法:

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

定义的 equals 与 == 是等效的。

2、覆盖了 equals 方法
类中如果覆盖了 equals 方法,就要根据具体的代码来确定 equals 方法的作用了,覆盖后一般都是通过对象的内容是否相等来判断对象是否相等。下面是String类对equals进行了重写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}

即 String 中 equals 方法判断相等的步骤是:

1、若 A == B,即是同一个String对象,返回 true。

2、若对比对象是 String 类型则继续,否则返回 false。

3、判断 A、B 长度是否一样,不一样的话返回 false。

4、逐个字符比较,若有不相等字符,返回 false。

这里对 equals 重写需要注意五点:

  • 自反性:对任意引用值 X,x.equals(x) 的返回值一定为 true。
  • 对称性:对于任何引用值 x,y,当且仅当 y.equals(x) 返回值为 true 时,x.equals(y) 的返回值一定为 true。
  • 传递性:如果 x.equals(y) = true,y.equals(z) = true,则 x.equals(z) = true。
  • 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变。
  • 非空性:任何非空的引用值X,x.equals(null) 的返回值一定为 false。

实现高质量 equals 方法的诀窍:

1、使用 == 符号检查“参数是否为这个对象的引用”。如果是,则返回 true。

2、使用 instanceof 操作符检查“参数是否为正确的类型”。如果不是,则返回 false。一般来说,所谓“正确的类型”是指 equals 方法所在的那个类。

3、把参数转换成正确的类型。因为转换之前进行过 instanceof 测试,所以确保会成功。

4、对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功,则返回 true;否则返回 false。

5、当编写完成了 equals 方法之后,检查“对称性”、“传递性”、“一致性”。

hashcode

hashCode() 方法返回的就是一个数值,从方法的名称上就可以看出,其目的是生成一个 hash 码。hash 码的主要用途就是在对对象进行散列的时候作为 key 输入,据此很容易推断出,我们需要每个对象的 hash 码尽可能不同,这样才能保证散列的存取性能。事实上,Object 类提供的默认实现确实保证每个对象的 hash 码不同(在对象的内存地址基础上经过特定算法返回一个 hash 码)。哈希(Hash)实际上是个人名,由于他提出哈希算法的概念,所以就以他的名字命名了。 哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上

散列函数,散列算法,哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。好的散列函数在输入域中很少出现散列冲突

所有散列函数都有如下一个基本特性:
1、如果 a = b,则 h(a) = h(b)。
2、如果 a != b,则 h(a) 与 h(b) 可能得到相同的散列值。

想要明白,必须要先知道Java中的集合。  
总的来说,Java中的集合(Collection)有两类,一类是 List,再有一类是 Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。

那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?

这就是 Object.equals 方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有 1000 个元素,那么第 1001 个元素加入集合时,它就要调用 1000 次 equals 方法。这显然会大大降低效率。

于是,Java 采用了哈希表的原理。这样一来,当集合要添加新的元素时,先调用这个元素的 hashCode 方法,就一下子能定位到它应该放置的物理地址位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的 equals 方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用 equals 方法的次数就大大降低了,几乎只需要一两次。

eqauls 方法和 hashCode 方法的关系

Java 对于 eqauls 方法和 hashCode 方法是这样规定的:

1、同一对象上多次调用 hashCode() 方法,总是返回相同的整型值。
2、如果 a.equals(b),则一定有 a.hashCode() 一定等于 b.hashCode()。
3、如果 !a.equals(b),则 a.hashCode() 不一定等于 b.hashCode()。此时如果 a.hashCode() 总是不等于 b.hashCode(),会提高 hashtables 的性能。
4、a.hashCode() == b.hashCode() 则 a.equals(b) 可真可假。
5、a.hashCode()!= b.hashCode() 则 a.equals(b) 为假。

简而言之:
1、如果两个对象 equals,Java 运行时环境会认为他们的 hashcode 一定相等。
2、如果两个对象不 equals,他们的 hashcode 有可能相等。
3、如果两个对象 hashcode 相等,他们不一定 equals。
4、如果两个对象 hashcode 不相等,他们一定不 equals。

关于这两个方法的重要规范:
规范 1:若重写 equals(Object obj) 方法,有必要重写 hashcode() 方法,确保通过 equals(Object obj) 方法判断结果为 true 的两个对象具备相等的 hashcode() 返回值。说得简单点就是:“如果两个对象相同,那么他们的hashcode应该相等”。不过请注意:这个只是规范,如果你非要写一个类让 equals(Object obj)返回 true 而 hashcode() 返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了 Java 规范,程序也就埋下了BUG。

规范 2:如果 equals(Object obj) 返回 false,即两个对象“不相同”,并不要求对这两个对象调用 hashcode() 方法得到两个不相同的数。说的简单点就是:“如果两个对象不相同,他们的 hashcode 可能相同”。

为什么重写 equals 方法时总要重写 hashcode 方法

一个很常见的错误根源在于没有覆盖 hashCode 方法。在每个覆盖了 equals 方法的类中,也必须覆盖 hashCode 方法。如果不这样做的话,就会违反 Object.hashCode 的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括 HashMap、HashSet 和 Hashtable。

1、在应用程序的执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode 方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。

2、如果两个对象根据 equals() 方法比较是相等的,那么调用这两个对象中任意一个对象的 hashCode 方法都必须产生同样的整数结果。

3、如果两个对象根据 equals() 方法比较是不相等的,那么调用这两个对象中任意一个对象的 hashCode 方法,则不一定要产生相同的整数结果。但是如果让不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。

参考资源:
http://java-min.iteye.com/blog/1416727
https://blog.csdn.net/hla199106/article/details/46907725

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2019 kevinyangI All Rights Reserved.

UV : | PV :